Skip to content

大作业开发日志

1.1 词法阶段

ToyPL 风格词法分析实现规划

摘要

按 README.md 里整理出的 ToyPL 词法风格,先把作业的词法阶段单独做完,不进入语法和语义。实现目标是:把源代码稳定切成 Token,保留位置信息,支持题目要求的 int / float / string 常量、int / float 变量、6 种比较运算、if-else、print,并为可 选的 while / for 留好入口。

关键改动
  • 更新 src/main/java/com/weiqiang/caculator/lex/TokenType.java,把当前不完整的枚举扩成完整词法集合。
  • 新增 src/main/java/com/weiqiang/caculator/lex/Token.java,保存 type / value / row / col,风格对齐 ToyPL。
  • 新增 src/main/java/com/weiqiang/caculator/lex/Lexer.java,采用 单指针 + 当前行 + 当前列 + 前瞻字符 的扫描方式。
  • 新增 src/main/java/com/weiqiang/caculator/lex/MyException.java,统一抛出词法错误,错误信息带行列位置。
  • 词法阶段只负责“识别并产出 Token”,不做语法树构建,也不做类型检查。
Token 设计
  • 最小必需 Token:INT、FLOAT、STRING、ID、KW、IF、ELSE、PRINT、PLUS、MINUS、MUL、DIV、ASSIGN、COMP、LPAREN、RPAREN、 LBRACE、RBRACE、COMMA、SEMICOLON、EOF。
  • 其中 KW 用来表示类型关键字,value 区分 int 和 float,这样最接近 ToyPL 的编码方式。
  • COMP 统一表示 6 种比较运算,value 保存具体符号:> >= < <= == !=。
  • WHILE / FOR 作为可选扩展 Token 预留;如果你后面确认只做最小作业,也可以在 lexer 中先识别但不进入后续阶段。
  • MOD、AND、OR、NOT 这类 ToyPL 扩展项不纳入当前作业最小词法集,避免把词法范围做大。
扫描规则
  • 空白符、制表符、换行符直接跳过,换行只更新行号和列号。
  • 支持 // 单行注释,注释内容直到行尾都忽略。
  • 数字扫描:连续读取数字;遇到一个 . 就转为浮点扫描;若出现第二个 .,直接报错。
  • 字符串扫描:遇到 " 进入字符串状态,直到再次遇到 " 结束;如果到行尾或文件结束仍未闭合,报错。
  • 标识符扫描:以字母或下划线开头,后续可跟字母、数字、下划线;扫描完后查关键字表。
  • 关键字识别顺序:先识别完整单词,再判断是否为 int / float / if / else / print / while / for。
  • 运算符识别顺序:先看双字符运算符,再看单字符运算符;例如 ==、<=、>=、!= 必须优先于 =、<、>、!。
  • 扫描结束后追加一个 EOF,和 ToyPL 一致。
状态处理
  • analyzeTokensFromCode(String code):按行切分源码,逐行调用扫描逻辑。
  • analyzeTokensFromLine():在当前行上循环推进,遇到一个 Token 就立刻入表。
  • watchNextChar():只负责“看当前字符”,不前移指针,保持 ToyPL 式的前瞻结构。
  • extractNumberToken():专门抽取数字和小数。
  • extractStringToken():专门抽取字符串常量。
  • extractIdentifierOrKeyword():专门抽取标识符和关键字。
  • 扫描器内部建议用 StringBuilder 收集字面量内容,不用正则硬切,这样更贴近 ToyPL 的逐字符写法。
  • 所有状态转换都围绕当前字符种类做分派,保持实现简单、可读、易调试。
错误处理
  • 统一抛出 MyException,消息格式建议包含:
    • 错误代码
    • 原始行内容
    • 行号
    • 列号
    • 错误原因
  • 至少处理以下错误:
    • 非法字符
    • 数字格式错误
    • 重复小数点
    • 字符串未闭合
    • 非法运算符组合
  • 建议错误风格直接仿 ToyPL:一旦发现错误立即停止,不做容错继续扫描。
  • 词法阶段不处理括号是否配对、不处理语句是否完整,这些留给语法阶段。
测试
  • 基本识别测试:
    • int a = 12;
    • float b = 3.14;
    • print("hello");
  • 比较运算测试:
    • a > b
    • a >= b
    • a < b
    • a <= b
    • a == b
    • a != b
  • 控制流测试:
    • if (a < b) print(a); else print(b);
    • while (a < 10) print(a);
    • for (...) 若你选择保留扩展
  • 注释和空白测试:
    • 含空格、换行、// 注释混排的输入
  • 错误测试:
    • 12.3.4
    • "abc
    • @
    • a = = 3
  • 验收标准:
    • 合法输入能稳定产出正确 Token 序列
    • 每个 Token 都带准确行列信息
    • 非法输入能准确报错并停止
默认假设
  • 当前作业词法只做“识别”,不做语法和语义。
  • 类型关键字只需要 int 和 float,因为黑板题明确变量只支持这两类。
  • string 只作为字符串常量处理,不作为变量类型处理。
  • 为了贴近 ToyPL,比较运算统一用 COMP,具体符号放在 Token.value 中。
  • while / for 属于可选项,所以 lexer 先预留 Token,但不会影响最小作业实现。
已按你的要求处理:
  • src/main/java/com/weiqiang/caculator/lex/Token.java
  • src/main/java/com/weiqiang/caculator/lex/Lexer.java
  • src/main/java/com/weiqiang/caculator/lex/MyException.java

实现内容包括:

  • 识别 int / float / string、ID、if / else / while / for / print
  • 识别 = == != < <= > >=
  • 识别 + - * / ( ) { } [ ] , ;
  • 支持 // 注释
  • 保留行号、列号和 EOF
  • 词法错误统一抛 MyException

我还顺手把 pom.xml 配成了 Java 8 + UTF-8,并把 D:/project/javacode/caculator.gitignore 加上了 META-INF/ 和 tmp_jar/。

验证结果:mvn -q -DskipTests package 已通过。

1.2 词法阶段

语法树(初版)

Project AST

  program -> block
    block -> exprList
      exprList -> stmt
        stmt -> declare
          declare -> kw int id [;]
        stmt -> declare
          declare -> kw float id [= expr]
            expr -> arith
              arith -> term
                term -> factor FLOAT
        stmt -> expr
          expr -> IF (arith) block [ELSE block]
            arith -> term
              term -> factor ID
            block -> exprList
              stmt -> print
                print -> CALL print(arith)
                  arith -> term
                    term -> factor STRING
              stmt -> print
                print -> CALL print(arith)
                  arith -> term
                    term -> factor STRING
        stmt -> print
          print -> CALL print(arith)
            arith -> term
              term -> factor ID
              term -> factor BINARY(+)
                factor -> ID
                factor -> ID

我做了两件事:

  • 变量声明和赋值

  • print(...)

  • if-else

  • while

  • for

  • + - * / %

  • > >= < <= == !=

  • 括号表达式

  • 数字 / 字符串 / 标识符

    我也把 SyntaxNode.toTreeString() 做好了,后面你可以直接把 AST 打出来看树结构。

    • mvn -q -DskipTests package 已通过

    如果你要,我下一步可以继续把这个语法树接到一个“示例入口”里,给你写一个最小 main 来打印整棵树,方便你直接看解析结果。